﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.ServiceModel.Syndication;

namespace ServeurRSS_1_0.Source.Model
{
    class RobustXmlReader : System.Xml.XmlTextReader
    {
        readonly string[] _rss20DateTimeHints = { "pubDate" };
        readonly string[] _atom10DateTimeHints = { "updated", "published", "atom:updated" };
        readonly string[] _localeTags = { "language" };

        public RobustXmlReader(Stream stream)
            : base(stream)
        {
        }

        bool _resolveRss20DateTime = false;
        bool _resolveAtom10DateTime = false;
        bool _resolveLocale = false;

        CultureInfo _cultureInfo = CultureInfo.CurrentCulture;

        public override bool IsStartElement(string localname, string ns)
        {
            _resolveRss20DateTime = false;
            _resolveAtom10DateTime = false;

            if (_rss20DateTimeHints.FirstOrDefault(x => x == localname) != null)
                _resolveRss20DateTime = true;
            else if (_atom10DateTimeHints.FirstOrDefault(x => x == localname) != null)
                _resolveAtom10DateTime = true;
            else if (_localeTags.FirstOrDefault(x => x == localname) != null)
                _resolveLocale = true;

            return base.IsStartElement(localname, ns);
        }

        // This actually tries to parse
        public override string ReadString()
        {
            string value = base.ReadString();

            if (_resolveLocale)
            {
                _resolveLocale = false;

                try
                {
                    _cultureInfo = new CultureInfo(value);
                }
                catch
                {
                    // Nothing critical.
                }

                return value;
            }

            try
            {
                // Determine wether were about to parse a dateTime and if so, try to parse it ourself
                // This way, we can make sure the datetime is valid before the actual parser parsers the datetime.
                // Note that for this implementation i just use the actual method that is going to parse the datetime,
                // This ofcourse should be made illegal and therefore i strongly suggest you make your own exceptional implementation
                if (_resolveRss20DateTime)
                {
                    MethodInfo objMethod = typeof(Rss20FeedFormatter).GetMethod("DateFromString", BindingFlags.NonPublic | BindingFlags.Static);
                    objMethod.Invoke(null, new object[] { value, this });
                }
                else if (_resolveAtom10DateTime)
                {
                    MethodInfo objMethod = typeof(Atom10FeedFormatter).GetMethod("DateFromString", BindingFlags.NonPublic | BindingFlags.Instance);
                    objMethod.Invoke(new Atom10FeedFormatter(), new object[] { value, this });
                }

                return value;
            }
            catch (TargetInvocationException)
            {
                // This is most likely to happen when datefromstring method fails
                // which tells us the specific DateTime is not conform to the specifications
                // We should try and parse the datetime ourself

                // Failed dates from MSDN magazine feed
                // 10/26/2011 00:00:00 GMT
                // 5/2/2011 00:00:00 GMT
                // Mon, 14 Apr 2008 13:17:18 GMT 00:00:00 GMT
                string[] formats =
            {
                "M/d/yyyy hh:mm:ss GMT",
                "ddd, dd MMM yyyy HH:mm:ss GMT 00:00:00 GMT",
            };

                const string syndicationTimeFormat = "R";
                DateTimeOffset dateTimeOffset;
                if (DateTimeOffset.TryParseExact(value, formats, _cultureInfo, System.Globalization.DateTimeStyles.None, out dateTimeOffset))
                    return dateTimeOffset.ToString(syndicationTimeFormat);
                else
                {
                    // We can't fail reading. Better we return incorrect date
                    return DateTimeOffset.UtcNow.ToString(syndicationTimeFormat);
                }
            }
        }
    }

}